﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace Kaleta.Xna.Timing
{

    /// <summary>
    /// Defines a value type that is dependent on time. Begins with full value, after draining(decreasing) value, 
    /// the value will replenish to its full value at specified time replenishRate.
    /// </summary>
    public abstract class Recharger
    {
        /// <summary>
        /// Specifies the time needed to fully recharge. In milliseconds.
        /// </summary>
        public float RechargeDuration
        {
            get { return _rechargeDuration; }
            set
            {
                _rechargeDuration = value;
                RechargeRate = Capacity / RechargeDuration;
            }
        }
        private float _rechargeDuration;

        /// <summary>
        /// Specifies the value when fully recharged.
        /// </summary>
        public float Capacity
        {
            get { return _capacity; }
            set
            {
                _capacity = value;
                RechargeRate = Capacity / RechargeDuration;
            }
        }

        private float _capacity;
        /// <summary>
        /// Specifies the current value.
        /// </summary>
        public float CurrentValue { get; protected set; }

        /// <summary>
        /// Specifies the rate at which the value is replenished during time.
        /// </summary>
        public float RechargeRate { get; private set; }

        /// <summary>
        /// Creates new instance of Recharger. Note that there are two types: 
        /// 1. Recharger which value cannot decrease below zero
        /// 2. Recharger which value can decrease below zero
        /// </summary>
        /// <param name="duration"></param>
        /// <param name="fullRechargeValue"></param>
        /// <param name="canDrainBelowZero"></param>
        public Recharger(float duration, float fullRechargeValue)
        {
            this.Capacity = this.CurrentValue = fullRechargeValue;

            this.RechargeDuration = duration;
            this.lastUpdate = -1;
        }

        /// <summary>
        /// Updates the value of this recharger: replenished it by the recharge rate.
        /// </summary>
        /// <param name="gameTime"></param>
        public void Update(GameTime gameTime)
        {
            if (CurrentValue == Capacity)
            {
                lastUpdate = -1;
                return;
            }

            //if lastUpdate=-1 we are about to start:
            if (lastUpdate == -1) lastUpdate = gameTime.TotalGameTime.TotalMilliseconds;

            CurrentValue += (float)((gameTime.TotalGameTime.TotalMilliseconds - lastUpdate) * RechargeRate);
            CurrentValue = Math.Min(CurrentValue, Capacity);

            lastUpdate = gameTime.TotalGameTime.TotalMilliseconds;
        }

        /// <summary>
        /// Indicates wheter this recharger can drain this amount from its pool.
        /// </summary>
        /// <returns></returns>
        //public virtual bool CanDrain(float amount);

        /// <summary>
        /// If the recharger can be decreased (drained) by this amount, decreases it and returns true. Otherwise false.
        /// </summary>
        /// <param name="amount"></param>
        /// <returns></returns>
        //public abstract bool TryDrain(float amount);

        /// <summary>
        /// If the recharger can be decreased (drained) by this amount, decreases it. Otherwise InvalidOperationException.
        /// </summary>
        /// <param name="amount"></param>
        /// <returns></returns>
        //public abstract void Drain(float amount);

        private double lastUpdate;
    }

    /// <summary>
    /// Defines a value type that is dependent on time. Begins with full value, after draining(decreasing) value, 
    /// the value will replenish to its full value at specified replenishRate.
    /// This recharger cannot drop its value below zero. Additionaly it cannot drain by amount which is more than current value at once.
    /// </summary>
    public class PositiveRecharger : Recharger
    {
        public PositiveRecharger(float duration, float capacity) : base(duration, capacity) { }
        /// <summary>
        /// Indicates whether this recharger can drain this amount from its pool (current value).
        /// </summary>
        /// <returns></returns>
        public bool CanDrain(float amount)
        {
            if (CurrentValue < amount) return false;
            return true;
        }
        /// <summary>
        /// If the recharger can be decreased (drained) by this amount, decreases it and returns true. Otherwise false.
        /// </summary>
        /// <param name="amount"></param>
        /// <returns></returns>
        public bool TryDrain(float amount)
        {
            if (CurrentValue < amount) return false;

            CurrentValue -= amount;
            return true;
        }

        /// <summary>
        /// If the recharger can be decreased (drained) by this amount, decreases it. Otherwise InvalidOperationException.
        /// </summary>
        /// <param name="amount"></param>
        /// <returns></returns>
        public void Drain(float amount)
        {
            if (!CanDrain(amount))
                throw new InvalidOperationException("The argument value exceeds rechargers current value");
            CurrentValue -= amount;
        }

    }
    /// <summary>
    /// Defines a value type that is dependent on time. Begins with full value, after draining(decreasing) value, 
    /// the value will replenish to its full value at specified replenishRate.
    /// This recharger can drain regardless of current value to specified Minimum value.
    /// </summary>
    public class NegativeRecharger : Recharger
    {
        /// <summary>
        /// Creates recharger, which Minimum value is 0. This means, if drained by whatever amount, the current value never drops bellow zero.
        /// </summary>
        /// <param name="duration"></param>
        /// <param name="capacity"></param>
        public NegativeRecharger(float duration, float capacity) : base(duration, capacity)
        {
            min = 0;
        }

        public NegativeRecharger(float duration, float capacity, float minValue)
            : base(duration, capacity)
        {
            min = minValue;
        }

        /// <summary>
        /// Draines the specified amount. After draining, the current value is allways equal or more than Minimum.
        /// </summary>
        /// <param name="amount"></param>
        /// <returns></returns>k
        public void Drain(float amount)
        {
            CurrentValue = Math.Max(CurrentValue - amount, min);
        }
        /// <summary>
        /// Returns allways true. This is because NegativeRecharger can be drained by whatever amount (to specified Minimum).
        /// </summary>
        /// <returns></returns>
        public bool CanDrain(float amount)
        {
            return true;
        }
        /// <summary>
        /// Drains this amount from current value and returns true.  
        /// After draining, the current value is allways equal or more than Minimum.
        /// </summary>
        /// <param name="amount"></param>
        /// <returns></returns>
        public bool TryDrain(float amount)
        {
            Drain(amount);
            return true;
        }

        private float min;

    }

    /// <summary>
    /// Defines a value type that is dependent on time. 
    /// Begins with value of True, after draining(decreasing), 
    /// the recharger will replenish to value of True after the specified duration.
    /// </summary>
    public class BoolRecharger : Recharger
    {
        public BoolRecharger(float duration) : base(duration, 1) { }


        /// <summary>
        /// If this recharger is fully recharged, returns true. Otherwise false.
        /// </summary>
        /// <param name="amount"></param>
        /// <returns></returns>
        public bool TryDrain()
        {
            if (CurrentValue < 1) return false;

            CurrentValue -= 1.0f;
            return true;
        }
        /// <summary>
        /// If the recharger can be decreased (drained) by this amount, decreases it. Otherwise InvalidOperationException.
        /// </summary>
        /// <param name="amount"></param>
        /// <returns></returns>
        public void Drain()
        {
            if (!CanDrain())
                throw new InvalidOperationException("The argument value exceeds rechargers current value");
            CurrentValue -= 1;
        }
        /// <summary>
        /// Indicates wheter this recharger can drain this amount from its pool.
        /// </summary>
        /// <returns></returns>
        public bool CanDrain()
        {
            if (CurrentValue < 1) return false;
            return true;
        }
    }
}
